export class YtjFile {
  constructor(data) {
    this.dom = null
    // files.type = waiting（等待上传）|| loading（上传中）|| success（成功） || fail（失败）
    this.files = new Map()
    this.debug = data.debug || false
    this.id = data.id
    this.width = data.width
    this.height = data.height
    this.option = data.option
    this.instantly = data.instantly
    this.prohibited = data.prohibited
    this.onchange = data.onchange
    this.onprogress = data.onprogress
    this.uploadHandle = this._uploadHandle
    // #ifdef MP-WEIXIN
    this.uploadHandle = this._uploadHandleWX
    // #endif
  }

  /**
   * 创建File节点
   * @param {string}path webview地址
   */
  create(path) {
    if (!this.dom) {
      // #ifdef H5
      let dom = document.createElement('input')
      dom.type = 'file'
      dom.value = ''
      dom.style.height = this.height
      dom.style.width = this.width
      dom.style.position = 'absolute'
      dom.style.top = 0
      dom.style.left = 0
      dom.style.right = 0
      dom.style.bottom = 0
      dom.style.opacity = 0
      dom.style.zIndex = 999
      dom.accept = this.prohibited.accept
      if (this.prohibited.count > 1) {
        dom.multiple = 'multiple'
      }
      dom.onchange = (event) => {
        for (let file of event.target.files) {
          if (this.files.size >= this.prohibited.count) {
            this.toast(`只允许上传${this.prohibited.count}个文件`)
            this.dom.value = ''
            break
          }
          this.addFile(file)
        }

        this._uploadAfter()

        this.dom.value = ''
      }
      this.dom = dom
      // #endif

      // #ifdef APP-PLUS
      let styles = {
        top: '-200px',
        left: 0,
        width: '1px',
        height: '200px',
        background: 'transparent',
      }
      let extras = {
        debug: this.debug,
        instantly: this.instantly,
        prohibited: this.prohibited,
      }
      this.dom = plus.webview.create(path, this.id, styles, extras)
      this.setData(this.option)
      this._overrideUrlLoading()
      // #endif
      return this.dom
    }
  }

  /**
   * 设置上传参数
   * @param {object|string}name 上传参数,支持a.b 和 a[b]
   */
  setData() {
    let [name, value = ''] = arguments
    if (typeof name === 'object') {
      Object.assign(this.option, name)
    } else {
      this._setValue(this.option, name, value)
    }

    this.debug && console.log(JSON.stringify(this.option))

    // #ifdef APP-PLUS
    this.dom.evalJS(`vm.setData('${JSON.stringify(this.option)}')`)
    // #endif
  }

  /**
   * 上传
   * @param {string}name 文件名称
   */
  async upload(name = '') {
    if (!this.option.url) {
      throw Error('未设置上传地址')
    }

    // #ifndef APP-PLUS
    if (name && this.files.has(name)) {
      await this.uploadHandle(this.files.get(name))
    } else {
      for (let item of this.files.values()) {
        if (item.type === 'waiting' || item.type === 'fail') {
          await this.uploadHandle(item)
        }
      }
    }
    // #endif

    // #ifdef APP-PLUS
    this.dom && this.dom.evalJS(`vm.upload('${name}')`)
    // #endif
  }

  // 选择文件change
  addFile(file, isCallChange) {
    let name = file.name
    this.debug && console.log('文件名称', name, '大小', file.size)

    if (file) {
      // 限制文件格式
      let path = ''
      let suffix = name.substring(name.lastIndexOf('.') + 1).toLowerCase()
      let formats = this.prohibited.formats.toLowerCase()
      // #ifndef MP-WEIXIN
      path = URL.createObjectURL(file)
      // #endif
      // #ifdef MP-WEIXIN
      path = file.path
      // #endif
      if (formats && !formats.includes(suffix)) {
        this.toast(`不支持上传${suffix.toUpperCase()}格式文件`)
        return false
      }
      // 限制文件大小
      if (file.size > 1024 * 1024 * Math.abs(this.prohibited.size)) {
        this.toast(`附件大小请勿超过${this.prohibited.size}M`)
        return false
      }
      this.files.set(file.name, {
        file,
        path,
        name: file.name,
        size: file.size,
        progress: 0,
        type: 'waiting',
      })
      return true
    }
  }

  /**
   * 移除文件
   * @param {string}name 不传name默认移除所有文件，传入name移除指定name的文件
   */
  clear(name = '') {
    // #ifdef APP-PLUS
    this.dom && this.dom.evalJS(`vm.clear('${name}')`)
    // #endif

    if (!name) {
      this.files.clear()
    } else {
      this.files.delete(name)
    }
    return this.onchange(this.files)
  }

  /**
   * 提示框
   * @param {string}msg 轻提示内容
   */
  toast(msg) {
    uni.showToast({
      title: msg,
      icon: 'none',
    })
  }

  /**
   * 微信小程序选择文件
   * @param {number}count 可选择文件数量
   */
  chooseMessageFile(type, count) {
    wx.chooseMessageFile({
      count: count,
      type: type,
      success: ({ tempFiles }) => {
        for (let file of tempFiles) {
          this.addFile(file)
        }
        this._uploadAfter()
      },
      fail: () => {
        this.toast(`打开失败`)
      },
    })
  }

  _copyObject(obj) {
    if (typeof obj !== 'undefined') {
      return JSON.parse(JSON.stringify(obj))
    } else {
      return obj
    }
  }

  /**
   * 自动根据字符串路径设置对象中的值 支持.和[]
   * @param	{Object} dataObj 数据源
   * @param	{String} name 支持a.b 和 a[b]
   * @param	{String} value 值
   * setValue(dataObj, name, value);
   */
  _setValue(dataObj, name, value) {
    // 通过正则表达式  查找路径数据
    let dataValue
    if (typeof value === 'object') {
      dataValue = this._copyObject(value)
    } else {
      dataValue = value
    }
    let regExp = new RegExp('([\\w$]+)|\\[(:\\d)\\]', 'g')
    const patten = name.match(regExp)
    // 遍历路径  逐级查找  最后一级用于直接赋值
    for (let i = 0; i < patten.length - 1; i++) {
      let keyName = patten[i]
      if (typeof dataObj[keyName] !== 'object') dataObj[keyName] = {}
      dataObj = dataObj[keyName]
    }
    // 最后一级
    dataObj[patten[patten.length - 1]] = dataValue
    this.debug && console.log('参数更新后', JSON.stringify(this.option))
  }

  _uploadAfter() {
    this.onchange(this.files)
    this.instantly && this.upload()
  }

  _overrideUrlLoading() {
    this.dom.overrideUrlLoading({ mode: 'reject' }, (e) => {
      let { retype, item, files, end } = this._getRequest(e.url)
      let _this = this
      switch (retype) {
        case 'updateOption':
          this.dom.evalJS(`vm.setData('${JSON.stringify(_this.option)}')`)
          break
        case 'change':
          try {
            _this.files = new Map([..._this.files, ...JSON.parse(unescape(files))])
          } catch (e) {
            return console.error('出错了，请检查代码')
          }
          _this.onchange(_this.files)
          break
        case 'progress':
          try {
            item = JSON.parse(unescape(item))
          } catch (e) {
            return console.error('出错了，请检查代码')
          }
          _this._changeFilesItem(item, end)
          break
        default:
          break
      }
    })
  }

  _getRequest(url) {
    let theRequest = new Object()
    let index = url.indexOf('?')
    if (index != -1) {
      let str = url.substring(index + 1)
      let strs = str.split('&')
      for (let i = 0; i < strs.length; i++) {
        theRequest[strs[i].split('=')[0]] = unescape(strs[i].split('=')[1])
      }
    }
    return theRequest
  }

  _changeFilesItem(item, end = false) {
    this.debug && console.log('onprogress', JSON.stringify(item))
    this.onprogress(item, end)
    this.files.set(item.name, item)
  }

  _uploadHandle(item) {
    item.type = 'loading'
    delete item.responseText
    return new Promise((resolve, reject) => {
      this.debug && console.log('option', JSON.stringify(this.option))
      let { url, name, method = 'POST', header, formData } = this.option
      let form = new FormData()
      for (let keys in formData) {
        form.append(keys, formData[keys])
      }
      form.append(name, item.file)
      let xmlRequest = new XMLHttpRequest()
      xmlRequest.open(method, url, true)
      for (let keys in header) {
        xmlRequest.setRequestHeader(keys, header[keys])
      }

      xmlRequest.upload.addEventListener(
        'progress',
        (event) => {
          if (event.lengthComputable) {
            let progress = Math.ceil((event.loaded * 100) / event.total)
            if (progress <= 100) {
              item.progress = progress
              this._changeFilesItem(item)
            }
          }
        },
        false,
      )

      xmlRequest.ontimeout = () => {
        console.error('请求超时')
        item.type = 'fail'
        this._changeFilesItem(item, true)
        return resolve(false)
      }

      xmlRequest.onreadystatechange = (ev) => {
        if (xmlRequest.readyState == 4) {
          if (xmlRequest.status == 200) {
            this.debug && console.log('上传完成：' + xmlRequest.responseText)
            item['responseText'] = xmlRequest.responseText
            item.type = 'success'
            this._changeFilesItem(item, true)
            return resolve(true)
          } else if (xmlRequest.status == 0) {
            console.error(
              'status = 0 :请检查请求头Content-Type与服务端是否匹配，服务端已正确开启跨域，并且nginx未拦截阻止请求',
            )
          }
          console.error('--ERROR--：status = ' + xmlRequest.status)
          item.type = 'fail'
          this._changeFilesItem(item, true)
          return resolve(false)
        }
      }
      xmlRequest.send(form)
    })
  }

  _uploadHandleWX(item) {
    item.type = 'loading'
    delete item.responseText
    return new Promise((resolve, reject) => {
      this.debug && console.log('option', JSON.stringify(this.option))
      let form = { filePath: item.file.path, ...this.option }
      form['fail'] = ({ errMsg = '' }) => {
        console.error('--ERROR--：' + errMsg)
        item.type = 'fail'
        this._changeFilesItem(item, true)
        return resolve(false)
      }
      form['success'] = (res) => {
        if (res.statusCode == 200) {
          this.debug &&
            console.log(
              '上传完成,微信端返回不一定是字符串，根据接口返回格式判断是否需要JSON.parse：' +
                res.data,
            )
          item['responseText'] = res.data
          item.type = 'success'
          this._changeFilesItem(item, true)
          return resolve(true)
        }
        item.type = 'fail'
        this._changeFilesItem(item, true)
        return resolve(false)
      }

      let xmlRequest = uni.uploadFile(form)
      xmlRequest.onProgressUpdate(({ progress = 0 }) => {
        if (progress <= 100) {
          item.progress = progress
          this._changeFilesItem(item)
        }
      })
    })
  }
}
